昨天僅介紹跳脫scaffold所產生的UI,自行撰寫註冊使用者網頁,但仍不完整,因User與Profile是一對一關係,故註冊時應合併兩者所有欄位進行註冊,Grails對於處理這樣的註冊表單相當方便,也是因為binddata的觀念,再來則是Grails中如何上傳圖片,將以另外一個小例子介紹,合併今日的範例就是一個在現有的Domain class(User, Profile)之下完整的使用者註冊表單。
其實我們的userController無需做任何code的修改關鍵在於昨日程式碼中的這一行
def user=new User(params)
//params是map,以當作constructor參數建立一個新的user物件
建立一個新的user物件,根據User的定義當然包含以下屬性:
String userId
String password
String personalPage
Date dateCreated
static hasOne = [profile : Profile]
//1對1 mapping
static hasMany = [posts:Post, tags:Tag, following: User]
所以只要params裡有對應之欄位有值,都會存入建立的user物件裡,當然也包括一對一的profile變數,故只要在GSP裡加入profile有關的欄位要求使用者輸入,則將自動new一個Profile物件對應到新的user,故register.gsp中g:form內新增程式碼如下如下,把user.profile.相關欄位屬性加入即可。
<dt>Full Name</dt>
<dd>
<g:textField name="profile.fullName" value="${user?.profile?.fullName}"/>
</dd>
<dt>Education</dt>
<dd>
<g:textField name="profile.education" value="${user?.profile?.education}"/>
</dd>
<dt>Email</dt>
<dd>
<g:textField name="profile.email" value="${user?.profile?.email}"/>
</dd>
<dt>city</dt>
<dd>
<g:textField name="profile.city" value="${user?.profile?.city}"/>
</dd>
run-app,原register.gsp網頁顯示如下:
填好送出後,顯示網頁如下:
再來介紹如何上傳圖片,先以一個簡單的uploadform表單為例,故我們新增->ImageController以及uploadform.gsp來處理上傳圖片的相關動作,之前一值忘記提,當我們新增一個controller時,預設導向都是
def index(){}
,以本例來說,我們將直接導向uploadform.gsp, 可參閱等一下的程式碼。
接著要介紹Grals所提供的Command Objects這個物件,這個物件主要的功能在於把網頁表單的資料存在這個Command Object裡,相當將params的參數bind在這個物件的屬性裡,通常命名為xxxCommand,Grails將自動把params的參數值指定給Command Object中屬性,以本例來說我們建立一個PhotoUploadCommand{}物件裡面有userId跟photo兩個屬性。
uploadform.gsp裡我們希望選擇使用者並上傳圖片 code如下:
<title>Upload Image Test</title>
<meta name="layout" content="main">
<h1>Upload user's image</h1>
<g:uploadForm action="upload">
<p>User Id:
<g:select name="userId" from="${userList}" optionKey="userId" optionValue="userId" />
<p/>
Photo: <input name="photo" type="file" />
<g:submitButton name="upload" value="Upload"/>
</g:uploadForm>
當需要上傳的時候,我們需要使用的是g:uploadForm,另外combobox可以用g:select這個標籤來實作,combobox的值可以從ImageController帶過來,感覺蠻方便的,photo部分則須注意type為file
在ImageController裡,我們必須
1.定義PhotoUploadCommand物件
1.定義uploadform,並提供userlist
2.定義upload這個方法處理照片上傳的動作,傳入參數就如剛剛所提的command object-PhotoUploadCommand,該物件會有表單資料,photo參數則指定給user.profile.photo,完成後導向UseController並將結果顯示於profiles.gsp
我們先來看profile.gsp,該網頁將顯示該使用者相片以及相關資料,code如下
<title>${profile.fullName} Profile</title>
<meta name="layout" content="main"/>
<style>
.profilePic {
border: 1px dotted gray;
background: lightyellow;
padding: 1em;
font-size: 1em;
}
</style>
<div class="profilePic">
<g:if test="${profile.photo}">
<img src="${createLink(controller:'image', action: 'showImage', id: profile.user.userId)}"/>
</g:if>
<p>Profile for <strong>${profile.fullName}</strong></p>
<p>Education: ${profile.education}</p>
<p>Email: ${profile.email}</p>
<p>City: ${profile.city}</p>
</div>
要顯示圖片於GSP,由於圖片剛剛我們是存在資料庫,所以要將圖片從資料庫撈出來,必須借助creatLink這個方法,Controller指定為ImageController,且還需coding showImage方法以及提供對應之使用者id參數,才能正確顯示圖面
故ImageController完整程式碼如下:
class ImageController {
def index(){
redirect(action:"uploadform")
//直接導向uploadform.gsp
}
def upload(PhotoUploadCommand puc){
def user = User.where {userId =~ puc.userId}.get()
user.profile.photo=puc.photo
//將表單中photo資料指定給user.profile.photo
redirect(controller:"user",action:'profile', id:puc.userId)
}
def uploadform(){
[userList:User.list()]
//提供userList
}
def showImage(String id){
def user = User.where {userId =~ id}.get()
if(user?.profile?.photo){
response.setContentLength(user.profile.photo.size())
//說實話我不知道為什麼要setContentLength
response.outputStream.write(user.profile.photo)
//將圖片畫在網頁上
}else{
response.sendError(404)
}
}
}
class PhotoUploadCommand{
String userId //相當於userId=params.userId
byte[] photo //相當於photo=params.photo
}
從ImageController pass參數到PostController,理所當然,PostController已必須撰寫相對應的code來render profile.gsp,從profile.gsp中尉減少變數長度,不需要在user.profile.xxx,故我們可以指定傳給profile.gsp時就是profile物件,故於UserController必須新增程式碼如下:
def profile(String id){
def user=User.where {userId =~ id}.get()
if(!user){
response.sendError(404)
}else{
[profile:user.profile]
//直接提供profile物件
}
}
還有一點說明,其實這過程當中,Server都不須重新啟動,當有檔案更新時,Grails將自動complile,立即生效的,截圖如下:
測試上傳圖面網頁如下:
新增成功網頁如下:
最後想跟大家分享的是,即便有書邊看邊學,看似容易,但也是需要時間try跟debug,原本以為很容易的事情,其實都是需要時間的,例如今天我的GGTS的console就是這個樣子....即便今日的範例很簡單
| Loading Grails 2.2.3
| Configuring classpath.
| Environment set to development.....
| Packaging Grails application....
| Compiling 1 source files.....
| Running Grails application十月 18, 2013 9:22:15 下午 org.apache.coyote.AbstractProtocol init
資訊: Initializing ProtocolHandler ["http-bio-8080"]
十月 18, 2013 9:22:15 下午 org.apache.catalina.core.StandardService startInternal
資訊: Starting service Tomcat
十月 18, 2013 9:22:15 下午 org.apache.catalina.core.StandardEngine startInternal
資訊: Starting Servlet Engine: Apache Tomcat/7.0.39
十月 18, 2013 9:22:15 下午 org.apache.catalina.startup.ContextConfig getDefaultWebXmlFragment
資訊: No global web.xml found
十月 18, 2013 9:22:15 下午 org.apache.catalina.core.ApplicationContext log
資訊: Initializing Spring root WebApplicationContext
| Server running. Browse to http://localhost:8080/JasonMicroBlog
| Compiling 1 source files.
| Error 2013-10-18 21:23:28,849 [http-bio-8080-exec-9] ERROR errors.GrailsExceptionResolver - NullPointerException occurred when processing request: [GET] /JasonMicroBlog/user/profile/Jason
Cannot get property 'profile' on null object. Stacktrace follows:
Message: Error processing GroovyPageView: Error executing tag <sitemesh:wrapTitleTag>: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
Line | Method
->> 464 | doFilter in \grails-app\views\user\profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Caused by GrailsTagException: Error executing tag <sitemesh:wrapTitleTag>: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
->> 3 | doCall in D:/groovy/JasonMicroBlog/grails-app/views/user/profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Caused by GroovyPagesException: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
->> 3 | doCall in D:/groovy/JasonMicroBlog/grails-app/views/user/profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Caused by NullPointerException: Cannot get property 'profile' on null object
->> 3 | doCall in D__groovy_JasonMicroBlog_grails_app_views_user_profile_gsp$_run_closure1_closure3_closure7_closure8
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 13 | run in D__groovy_JasonMicroBlog_grails_app_views_user_profile_gsp
| 195 | doFilter in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter in grails.plugin.cache.web.filter.AbstractFilter
| 1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 615 | run in java.util.concurrent.ThreadPoolExecutor$Worker
^ 744 | run . . . in java.lang.Thread
| Compiling 1 source files.....
| Compiling 1 source files.
| Error 2013-10-18 21:25:06,806 [http-bio-8080-exec-4] ERROR errors.GrailsExceptionResolver - NullPointerException occurred when processing request: [GET] /JasonMicroBlog/user/profile/Jason
Cannot get property 'profile' on null object. Stacktrace follows:
Message: Error processing GroovyPageView: Error executing tag <sitemesh:wrapTitleTag>: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
Line | Method
->> 464 | doFilter in \grails-app\views\user\profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Caused by GrailsTagException: Error executing tag <sitemesh:wrapTitleTag>: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
->> 3 | doCall in D:/groovy/JasonMicroBlog/grails-app/views/user/profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Caused by GroovyPagesException: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
->> 3 | doCall in D:/groovy/JasonMicroBlog/grails-app/views/user/profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Caused by NullPointerException: Cannot get property 'profile' on null object
->> 3 | doCall in D__groovy_JasonMicroBlog_grails_app_views_user_profile_gsp$_run_closure1_closure3_closure7_closure8
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 13 | run in D__groovy_JasonMicroBlog_grails_app_views_user_profile_gsp
| 195 | doFilter in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter in grails.plugin.cache.web.filter.AbstractFilter
| 1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 615 | run in java.util.concurrent.ThreadPoolExecutor$Worker
^ 744 | run . . . in java.lang.Thread
| Compiling 1 source files.....
| Compiling 1 source files.
| Error 2013-10-18 21:25:46,979 [http-bio-8080-exec-8] ERROR errors.GrailsExceptionResolver - NullPointerException occurred when processing request: [GET] /JasonMicroBlog/user/profile/Jason
Cannot get property 'profile' on null object. Stacktrace follows:
Message: Error processing GroovyPageView: Error executing tag <sitemesh:wrapTitleTag>: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
Line | Method
->> 464 | doFilter in \grails-app\views\user\profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Caused by GrailsTagException: Error executing tag <sitemesh:wrapTitleTag>: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
->> 3 | doCall in D:/groovy/JasonMicroBlog/grails-app/views/user/profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Caused by GroovyPagesException: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
->> 3 | doCall in D:/groovy/JasonMicroBlog/grails-app/views/user/profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Caused by NullPointerException: Cannot get property 'profile' on null object
->> 3 | doCall in D__groovy_JasonMicroBlog_grails_app_views_user_profile_gsp$_run_closure1_closure3_closure7_closure8
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 13 | run in D__groovy_JasonMicroBlog_grails_app_views_user_profile_gsp
| 195 | doFilter in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter in grails.plugin.cache.web.filter.AbstractFilter
| 1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 615 | run in java.util.concurrent.ThreadPoolExecutor$Worker
^ 744 | run . . . in java.lang.Thread
| Compiling 1 source files.....
| Compiling 1 source files.....
| Compiling 1 source files.
| Error 2013-10-18 21:29:20,689 [http-bio-8080-exec-1] ERROR errors.GrailsExceptionResolver - NullPointerException occurred when processing request: [GET] /JasonMicroBlog/image/profile/Jason
Cannot get property 'fullName' on null object. Stacktrace follows:
Message: Error processing GroovyPageView: Error executing tag <sitemesh:wrapTitleTag>: Error evaluating expression [profile.fullName] on line [3]: Cannot get property 'fullName' on null object
Line | Method
->> 464 | doFilter in \grails-app\views\image\profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Caused by GrailsTagException: Error executing tag <sitemesh:wrapTitleTag>: Error evaluating expression [profile.fullName] on line [3]: Cannot get property 'fullName' on null object
->> 3 | doCall in D:/groovy/JasonMicroBlog/grails-app/views/image/profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Caused by GroovyPagesException: Error evaluating expression [profile.fullName] on line [3]: Cannot get property 'fullName' on null object
->> 3 | doCall in D:/groovy/JasonMicroBlog/grails-app/views/image/profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Caused by NullPointerException: Cannot get property 'fullName' on null object
->> 3 | doCall in D__groovy_JasonMicroBlog_grails_app_views_image_profile_gsp$_run_closure1_closure3_closure7_closure8
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 13 | run in D__groovy_JasonMicroBlog_grails_app_views_image_profile_gsp
| 195 | doFilter in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter in grails.plugin.cache.web.filter.AbstractFilter
| 1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 615 | run in java.util.concurrent.ThreadPoolExecutor$Worker
^ 744 | run . . . in java.lang.Thread
| Compiling 1 source files.....
| Compiling 1 source files.....
| Compiling 1 source files.
| Error 2013-10-18 22:03:15,081 [Thread-9] ERROR compiler.GrailsProjectWatcher - Compilation Error: startup failed:
D:\groovy\JasonMicroBlog\grails-app\controllers\com\JasonMicroBlog\UserController.groovy: 63: expecting '}', found ')' @ line 63, column 38.
def user=User.where {userId =~ id)}.get()
^
1 error
| Compiling 1 source files.....
| Compiling 1 source files.
| Error 2013-10-18 22:06:02,415 [http-bio-8080-exec-8] ERROR errors.GrailsExceptionResolver - MissingPropertyException occurred when processing request: [GET] /JasonMicroBlog/image/showImage/Jason
No such property: photo for class: grails.gorm.DetachedCriteria. Stacktrace follows:
Message: No such property: photo for class: grails.gorm.DetachedCriteria
Line | Method
->> 837 | propertyMissing in grails.gorm.DetachedCriteria
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 21 | showImage in com.JasonMicroBlog.ImageController$$EOKd0t0A
| 195 | doFilter . . . in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter in grails.plugin.cache.web.filter.AbstractFilter
| 1145 | runWorker . . . in java.util.concurrent.ThreadPoolExecutor
| 615 | run in java.util.concurrent.ThreadPoolExecutor$Worker
^ 744 | run . . . . . . in java.lang.Thread
| Error 2013-10-18 22:06:11,456 [http-bio-8080-exec-9] ERROR errors.GrailsExceptionResolver - MissingPropertyException occurred when processing request: [GET] /JasonMicroBlog/image/showImage/Jason
No such property: photo for class: grails.gorm.DetachedCriteria. Stacktrace follows:
Message: No such property: photo for class: grails.gorm.DetachedCriteria
Line | Method
->> 837 | propertyMissing in grails.gorm.DetachedCriteria
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 21 | showImage in com.JasonMicroBlog.ImageController$$EOKd0t0A
| 195 | doFilter . . . in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter in grails.plugin.cache.web.filter.AbstractFilter
| 1145 | runWorker . . . in java.util.concurrent.ThreadPoolExecutor
| 615 | run in java.util.concurrent.ThreadPoolExecutor$Worker
^ 744 | run . . . . . . in java.lang.Thread
| Error 2013-10-18 22:21:31,084 [http-bio-8080-exec-6] ERROR errors.GrailsExceptionResolver - MissingPropertyException occurred when processing request: [GET] /JasonMicroBlog/image/showImage/Jason
No such property: photo for class: grails.gorm.DetachedCriteria. Stacktrace follows:
Message: No such property: photo for class: grails.gorm.DetachedCriteria
Line | Method
->> 837 | propertyMissing in grails.gorm.DetachedCriteria
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 21 | showImage in com.JasonMicroBlog.ImageController$$EOKd0t0A
| 195 | doFilter . . . in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter in grails.plugin.cache.web.filter.AbstractFilter
| 1145 | runWorker . . . in java.util.concurrent.ThreadPoolExecutor
| 615 | run in java.util.concurrent.ThreadPoolExecutor$Worker
^ 744 | run . . . . . . in java.lang.Thread
| Compiling 1 source files.....
| Compiling 1 source files.....
| Compiling 1 source files.....
| Compiling 1 source files.....
| Compiling 1 source files.....
| Compiling 1 source files.
Controller的部分到今天暫告一段落,明天開始會跟大家分享view的部分,